using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Schema;
using LinqToEdmx;
using LinqToEdmx.Map;
using LinqToEdmx.Model.Conceptual;
using Magiq.Databases;

namespace Magiq.Providers.Entities.Support
{
    public class EntitiesTypeMapping : ITypeMapping
    {
        private readonly Type type;
        private IEnumerable<PropertyMapping> properties;
        private static string RemoveNamespaceOf(string typeName)
        {
            return typeName.Substring(typeName.LastIndexOf(".") + 1);

        }

        private bool TypeNameMatches(string typeName, string mappedName)
        {
            var fullname = mappedName.StartsWith("IsTypeOf(")
                           ? mappedName.Substring(10).TrimEnd(')')
                           : mappedName;
            
            var name = RemoveNamespaceOf(fullname);

            return name == typeName;
        }

        public EntitiesTypeMapping(Type type, Edmx edmx)
        {
            this.type = type;
            Edmx = edmx;
            EntityType = GetEntityType(type.Name);

            entitySetMapping = edmx.GetItems<EntitySetMapping>().SingleOrDefault(e=>e.EntityTypeMappings.Any(x => TypeNameMatches(type.Name, x.TypeName)));
        }

        private Type GetTypeFrom(string typeName, bool nullable)
        {
            var candidate = Type.GetType(string.Format("System.{0}", typeName));
            if(candidate == null)
            {
                return type.Assembly.GetType(typeName);
            }
            return nullable && candidate.IsValueType ? typeof (Nullable<>).MakeGenericType(candidate) : candidate;
        }

        private IEnumerable<PropertyMapping> PropertyMappings(IEnumerable<EntityProperty> properties)
        {
            return properties.Select(x => new PropertyMapping(x.Name, GetTypeFrom(((XmlAtomicValue) x.Type).Value, x.Nullable))); //TODO: Check type!
        }
        private Type GetEndType(NavigationProperty property)
        {
            return GetTypeFrom(property.ToRole, true); 
        }

        private IEnumerable<PropertyMapping> PropertyMappings(IEnumerable<NavigationProperty> properties)
        {
            return properties.Select(x => new PropertyMapping(x.Name,GetEndType(x)));
        }

        private readonly EntitySetMapping entitySetMapping;

        #region ITypeMapping Members

        public IEnumerable<PropertyMapping> Members
        {
            get { return properties ?? (properties = PropertyMappings(EntityType.Properties).Union(PropertyMappings(EntityType.NavigationProperties))); }
        }

        public string TableName
        {
            get { return entitySetMapping.Name; }
        }

        public bool IsEntity
        {
            get { return entitySetMapping != null; }
        }
        private EntityType GetEntityType(string name)
        {
            return Edmx.GetItems<EntityType>().SingleOrDefault(x => name == x.Name);
        }
        public IEnumerable<PropertyMapping> Ids
        {
            get
            {
                var key = EntityType.Key;
                var entity = EntityType;
                while (key == null)
                {
                    entity = GetEntityType(RemoveNamespaceOf(entity.BaseType));
                    key = entity.Key;
                } 

                var ids = key.PropertyRefs.Select(x => x.Name).ToList();
                return PropertyMappings(EntityType.Properties.Where(x => ids.Contains(x.Name)));
            }
        }

        public Edmx Edmx { get; private set; }

        public EntityType EntityType { get; private set; }

        public IEnumerable<Condition> Conditions
        {
            get
            {
                return entitySetMapping.EntityTypeMappings.Where(x => TypeNameMatches(type.Name, x.TypeName))
                    .SelectMany(x => x.MappingFragments)
                    .SelectMany(x => x.Conditions);
            }
        }


        public IEnumerable<string> ColumnsFor(string propertyName)
        {
            var scalar = entitySetMapping.EntityTypeMappings.Where(x => TypeNameMatches(type.Name, x.TypeName))
                .SelectMany(x => x.MappingFragments)
                .SelectMany(x => x.ScalarProperties)
                .SingleOrDefault(x => x.Name == propertyName);

            if (scalar != null)
                return new[] {scalar.ColumnName};

            var navigation = EntityType.NavigationProperties.SingleOrDefault(x => x.Name == propertyName);

            if (navigation != null)
            {
                var destinationType = type.GetProperty(propertyName).PropertyType.Name;
                var associationMapping = Edmx.GetItems<AssociationSetMapping>()
                    .Single(x => x.TypeName == navigation.Relationship);
                return associationMapping.EndProperties.Single(x => x.Name == destinationType)
                    .ScalarProperties.Select(x => x.ColumnName);
            }

            throw new Exception(string.Format("Columns for property {0} not found.", propertyName));
        }

        #endregion

        public string ColumnFor(string propertyName)
        {
            return ColumnsFor(propertyName).FirstOrDefault();
        }
    }
}